// Copyright 2016-present the Material Components for iOS authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#import "MDCFloatingButton.h"

#import "private/MDCFloatingButtonModeAnimator.h"
#import "private/MDCFloatingButtonModeAnimatorDelegate.h"
#import "MDCButton.h"
#import "MaterialShadowElevations.h"

#import <MDFInternationalization/MDFInternationalization.h>

static const CGFloat MDCFloatingButtonDefaultDimension = 56;
static const CGFloat MDCFloatingButtonMiniDimension = 40;
static const CGFloat MDCFloatingButtonDefaultImageTitleSpace = 8;
static const UIEdgeInsets internalLayoutInsets = (UIEdgeInsets){0, 16, 0, 24};

@interface MDCFloatingButton () <MDCFloatingButtonModeAnimatorDelegate>

@property(nonatomic, readonly)
    NSMutableDictionary<NSNumber *, NSMutableDictionary<NSNumber *, NSValue *> *>
        *shapeToModeToMinimumSize;

@property(nonatomic, readonly)
    NSMutableDictionary<NSNumber *, NSMutableDictionary<NSNumber *, NSValue *> *>
        *shapeToModeToMaximumSize;

@property(nonatomic, readonly)
    NSMutableDictionary<NSNumber *, NSMutableDictionary<NSNumber *, NSValue *> *>
        *shapeToModeToContentEdgeInsets;

@property(nonatomic, readonly)
    NSMutableDictionary<NSNumber *, NSMutableDictionary<NSNumber *, NSValue *> *>
        *shapeToModeToHitAreaInsets;

@property(nonatomic, readonly)
    NSMutableDictionary<NSNumber *, NSMutableDictionary<NSNumber *, NSNumber *> *>
        *shapeToModeToCenterVisibleArea;

@end

@implementation MDCFloatingButton {
  MDCFloatingButtonShape _shape;

  MDCFloatingButtonModeAnimator *_modeAnimator;
  // Allows us to perform masking effects during mode animations.
  UIView *_titleLabelContainerView;
}

+ (void)initialize {
  [[MDCFloatingButton appearance] setElevation:MDCShadowElevationFABResting
                                      forState:UIControlStateNormal];
  [[MDCFloatingButton appearance] setElevation:MDCShadowElevationFABPressed
                                      forState:UIControlStateHighlighted];
}

+ (CGFloat)defaultDimension {
  return MDCFloatingButtonDefaultDimension;
}

+ (CGFloat)miniDimension {
  return MDCFloatingButtonMiniDimension;
}

+ (instancetype)floatingButtonWithShape:(MDCFloatingButtonShape)shape {
  return [[[self class] alloc] initWithFrame:CGRectZero shape:shape];
}

- (instancetype)init {
  return [self initWithFrame:CGRectZero shape:MDCFloatingButtonShapeDefault];
}

- (instancetype)initWithFrame:(CGRect)frame {
  return [self initWithFrame:frame shape:MDCFloatingButtonShapeDefault];
}

- (instancetype)initWithFrame:(CGRect)frame shape:(MDCFloatingButtonShape)shape {
  self = [super initWithFrame:frame];
  if (self) {
    _shape = shape;
    [self commonMDCFloatingButtonInit];
    // The superclass sets contentEdgeInsets from defaultContentEdgeInsets before the _shape is set.
    // Set contentEdgeInsets again to ensure the defaults are for the correct shape.
    [self updateShapeAndAllowResize:NO];
  }
  return self;
}

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-designated-initializers"
// https://stackoverflow.com/questions/24458608/convenience-initializer-missing-a-self-call-to-another-initializer
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
  self = [super initWithCoder:aDecoder];
#pragma clang diagnostic pop
  if (self) {
    // Required to migrate any previously-archived FloatingButtons from .largeIcon shape value
    if (@(_shape).integerValue >= 2) {
      _shape = MDCFloatingButtonShapeDefault;
    }
    // Shape must be set first before the common initialization
    [self commonMDCFloatingButtonInit];

    [self updateShapeAndAllowResize:NO];
  }
  return self;
}

- (void)commonMDCFloatingButtonInit {
  _imageTitleSpace = MDCFloatingButtonDefaultImageTitleSpace;

  // Create a container view for titleLabel and add the titelLabel to it. This will enable us to
  // mask the titleLabel mode animations if desired, while acting effectively as a pass-through for
  // the superview layout logic.
  _titleLabelContainerView = [[UIView alloc] initWithFrame:self.bounds];
  _titleLabelContainerView.autoresizingMask =
      UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  [self insertSubview:_titleLabelContainerView belowSubview:self.titleLabel];
  _titleLabelContainerView.userInteractionEnabled = NO;
  [_titleLabelContainerView addSubview:self.titleLabel];

  const CGSize miniNormalSize =
      CGSizeMake(MDCFloatingButtonMiniDimension, MDCFloatingButtonMiniDimension);
  const CGSize defaultNormalSize =
      CGSizeMake(MDCFloatingButtonDefaultDimension, MDCFloatingButtonDefaultDimension);
  const CGSize defaultExpandedMinimumSize = CGSizeMake(0, 48);
  const CGSize defaultExpandedMaximumSize = CGSizeMake(328, 0);

  // Minimum size values for different shape + mode combinations
  NSMutableDictionary *miniShapeMinimumSizeDictionary =
      [@{@(MDCFloatingButtonModeNormal) : [NSValue valueWithCGSize:miniNormalSize]} mutableCopy];
  NSMutableDictionary *defaultShapeMinimumSizeDictionary = [@{
    @(MDCFloatingButtonModeNormal) : [NSValue valueWithCGSize:defaultNormalSize],
    @(MDCFloatingButtonModeExpanded) : [NSValue valueWithCGSize:defaultExpandedMinimumSize],
  } mutableCopy];
  _shapeToModeToMinimumSize = [@{
    @(MDCFloatingButtonShapeMini) : miniShapeMinimumSizeDictionary,
    @(MDCFloatingButtonShapeDefault) : defaultShapeMinimumSizeDictionary,
  } mutableCopy];

  // Maximum size values for different shape + mode combinations
  NSMutableDictionary *miniShapeMaximumSizeDictionary =
      [@{@(MDCFloatingButtonModeNormal) : [NSValue valueWithCGSize:miniNormalSize]} mutableCopy];
  NSMutableDictionary *defaultShapeMaximumSizeDictionary = [@{
    @(MDCFloatingButtonModeNormal) : [NSValue valueWithCGSize:defaultNormalSize],
    @(MDCFloatingButtonModeExpanded) : [NSValue valueWithCGSize:defaultExpandedMaximumSize],
  } mutableCopy];
  _shapeToModeToMaximumSize = [@{
    @(MDCFloatingButtonShapeMini) : miniShapeMaximumSizeDictionary,
    @(MDCFloatingButtonShapeDefault) : defaultShapeMaximumSizeDictionary,
  } mutableCopy];

  // Content edge insets values for different shape + mode combinations
  // .mini shape, .normal mode
  const UIEdgeInsets miniNormalContentInsets = UIEdgeInsetsMake(8, 8, 8, 8);
  NSMutableDictionary *miniShapeContentEdgeInsetsDictionary =
      [@{@(MDCFloatingButtonModeNormal) : [NSValue valueWithUIEdgeInsets:miniNormalContentInsets]}
          mutableCopy];
  _shapeToModeToContentEdgeInsets =
      [@{@(MDCFloatingButtonShapeMini) : miniShapeContentEdgeInsetsDictionary} mutableCopy];

  // Hit area insets values for different shape + mode combinations
  // .mini shape, .normal mode
  const UIEdgeInsets miniNormalHitAreaInset = UIEdgeInsetsMake(-4, -4, -4, -4);
  NSMutableDictionary *miniShapeHitAreaInsetsDictionary = [@{
    @(MDCFloatingButtonModeNormal) : [NSValue valueWithUIEdgeInsets:miniNormalHitAreaInset],
  } mutableCopy];
  _shapeToModeToHitAreaInsets = [@{
    @(MDCFloatingButtonShapeMini) : miniShapeHitAreaInsetsDictionary,
  } mutableCopy];

  _shapeToModeToCenterVisibleArea = [[NSMutableDictionary alloc] init];
}

#pragma mark - UIView

- (CGSize)sizeThatFits:(__unused CGSize)size {
  return [self intrinsicContentSize];
}

- (CGSize)intrinsicContentSizeForModeNormal {
  switch (_shape) {
    case MDCFloatingButtonShapeDefault:
      return CGSizeMake(MDCFloatingButtonDefaultDimension, MDCFloatingButtonDefaultDimension);
    case MDCFloatingButtonShapeMini:
      return CGSizeMake(MDCFloatingButtonMiniDimension, MDCFloatingButtonMiniDimension);
  }
}

- (CGSize)intrinsicContentSizeForModeExpanded {
  const CGSize intrinsicTitleSize =
      [self.titleLabel sizeThatFits:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)];
  const CGSize intrinsicImageSize =
      [self.imageView sizeThatFits:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)];
  CGFloat intrinsicWidth = intrinsicTitleSize.width + intrinsicImageSize.width +
                           self.imageTitleSpace + internalLayoutInsets.left +
                           internalLayoutInsets.right + self.contentEdgeInsets.left +
                           self.contentEdgeInsets.right;
  CGFloat intrinsicHeight = MAX(intrinsicTitleSize.height, intrinsicImageSize.height) +
                            self.contentEdgeInsets.top + self.contentEdgeInsets.bottom +
                            internalLayoutInsets.top + internalLayoutInsets.bottom;
  return CGSizeMake(intrinsicWidth, intrinsicHeight);
}

- (CGSize)intrinsicContentSize {
  CGSize contentSize = CGSizeZero;
  if (self.mode == MDCFloatingButtonModeNormal) {
    contentSize = [self intrinsicContentSizeForModeNormal];
  } else if (self.mode == MDCFloatingButtonModeExpanded) {
    contentSize = [self intrinsicContentSizeForModeExpanded];
  }

  if (self.minimumSize.height > 0) {
    contentSize.height = MAX(self.minimumSize.height, contentSize.height);
  }
  if (self.maximumSize.height > 0) {
    contentSize.height = MIN(self.maximumSize.height, contentSize.height);
  }
  if (self.minimumSize.width > 0) {
    contentSize.width = MAX(self.minimumSize.width, contentSize.width);
  }
  if (self.maximumSize.width > 0) {
    contentSize.width = MIN(self.maximumSize.width, contentSize.width);
  }

  return contentSize;
}

/*
 Performs custom layout when the FAB is in .expanded mode. Specifically, the layout algorithm is
 as follows:

 1. Inset the bounds by the value of `contentEdgeInsets` and use this as the layout bounds.
 2. Determine the intrinsic sizes of the imageView and titleLabel.
 3. Compute the space remaining for the titleLabel after accounting for the imageView and built-in
    alignment guidelines (internalLayoutInsets).
 4. Position the imageView along the leading (or trailing) edge of the button, inset by
    internalLayoutInsets.left (flipped for RTL).
 5. Position the titleLabel along the leading edge of its available space.
 6. Apply the imageEdgeInsets and titleEdgeInsets to their respective views.
 */
- (void)layoutSubviews {
  // We have to set cornerRadius before laying out subviews so that the boundingPath is correct.
  CGRect visibleBounds = UIEdgeInsetsInsetRect(self.bounds, self.visibleAreaInsets);
  self.layer.cornerRadius = CGRectGetHeight(visibleBounds) / 2;
  [super layoutSubviews];

  if (self.mode == MDCFloatingButtonModeNormal) {
    return;
  }

  // Position the imageView and titleView
  //
  // +------------------------------------+
  // |    |  |  |  CEI TOP            |   |
  // |CEI +--+  |+-----+              |CEI|
  // | LT ||SP||Title|              |RGT|
  // |    +--+  |+-----+              |   |
  // |    |  |  |  CEI BOT            |   |
  // +------------------------------------+
  //
  // (A) The same spacing on either side of the label.
  // (SP) The spacing between the image and title
  // (CEI) Content Edge Insets
  //
  // The diagram above assumes an LTR user interface orientation
  // and a .leadingIcon imageLocation for this button.

  const CGRect insetBounds = [self insetBoundsForBounds:self.bounds];

  const CGFloat imageViewWidth = CGRectGetWidth(self.imageView.bounds);
  const CGFloat boundsCenterY = CGRectGetMidY(insetBounds);
  CGFloat titleWidthAvailable = CGRectGetWidth(insetBounds);
  titleWidthAvailable -= imageViewWidth;
  titleWidthAvailable -= self.imageTitleSpace;

  const CGFloat availableHeight = CGRectGetHeight(insetBounds);
  CGSize titleIntrinsicSize =
      [self.titleLabel sizeThatFits:CGSizeMake(titleWidthAvailable, availableHeight)];

  const CGSize titleSize = CGSizeMake(MAX(0, MIN(titleIntrinsicSize.width, titleWidthAvailable)),
                                      MAX(0, MIN(titleIntrinsicSize.height, availableHeight)));

  CGPoint titleCenter;
  CGPoint imageCenter;
  BOOL isLTR =
      self.mdf_effectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionLeftToRight;
  BOOL isLeadingIcon = self.imageLocation == MDCFloatingButtonImageLocationLeading;

  // If we are LTR with a leading image, the image goes on the left.
  // If we are RTL with a trailing image, the image goes on the left.
  if ((isLTR && isLeadingIcon) || (!isLTR && !isLeadingIcon)) {
    const CGFloat imageCenterX = CGRectGetMinX(insetBounds) + (imageViewWidth / 2);
    const CGFloat titleCenterX =
        CGRectGetMaxX(insetBounds) - titleWidthAvailable + (titleSize.width / 2);
    titleCenter = CGPointMake(titleCenterX, boundsCenterY);
    imageCenter = CGPointMake(imageCenterX, boundsCenterY);
  }
  // If we are LTR with a trailing image, the image goes on the right.
  // If we are RTL with a leading image, the image goes on the right.
  else {
    const CGFloat imageCenterX = CGRectGetMaxX(insetBounds) - (imageViewWidth / 2);
    const CGFloat titleCenterX =
        CGRectGetMinX(insetBounds) + titleWidthAvailable - (titleSize.width / 2);
    imageCenter = CGPointMake(imageCenterX, boundsCenterY);
    titleCenter = CGPointMake(titleCenterX, boundsCenterY);
  }

  self.imageView.center = imageCenter;
  self.imageView.frame = UIEdgeInsetsInsetRect(self.imageView.frame, self.imageEdgeInsets);
  self.titleLabel.center = titleCenter;
  CGRect newBounds = CGRectStandardize(self.titleLabel.bounds);
  self.titleLabel.bounds = (CGRect){newBounds.origin, titleSize};
  self.titleLabel.frame = UIEdgeInsetsInsetRect(self.titleLabel.frame, self.titleEdgeInsets);
}

- (CGRect)insetBoundsForBounds:(CGRect)bounds {
  BOOL isLeadingIcon = self.imageLocation == MDCFloatingButtonImageLocationLeading;
  UIEdgeInsets adjustedLayoutInsets =
      (isLeadingIcon ? internalLayoutInsets : MDFInsetsFlippedHorizontally(internalLayoutInsets));
  return UIEdgeInsetsInsetRect(UIEdgeInsetsInsetRect(bounds, adjustedLayoutInsets),
                               self.contentEdgeInsets);
}

#pragma mark - Mode animator

- (MDCFloatingButtonModeAnimator *)modeAnimator {
  if (!_modeAnimator) {
    _modeAnimator =
        [[MDCFloatingButtonModeAnimator alloc] initWithTitleLabel:self.titleLabel
                                          titleLabelContainerView:_titleLabelContainerView];
    _modeAnimator.delegate = self;
  }
  return _modeAnimator;
}

#pragma mark MDCFloatingButtonModeAnimatorDelegate

- (void)floatingButtonModeAnimatorCommitLayoutChanges:(MDCFloatingButtonModeAnimator *)modeAnimator
                                                 mode:(MDCFloatingButtonMode)mode {
  [self sizeToFit];
}

#pragma mark - Property Setters/Getters

- (void)setShape:(MDCFloatingButtonShape)shape {
  if (_shape == shape) {
    return;
  }
  _shape = shape;
  [self updateShapeAndAllowResize:YES];
}

- (void)setMode:(MDCFloatingButtonMode)mode {
  [self setMode:mode animated:NO animateAlongside:nil completion:nil];
}

- (void)setMode:(MDCFloatingButtonMode)mode animated:(BOOL)animated {
  [self setMode:mode animated:animated animateAlongside:nil completion:nil];
}

- (void)setMode:(MDCFloatingButtonMode)mode
            animated:(BOOL)animated
    animateAlongside:(void (^)(void))animateAlongside
          completion:(void (^)(BOOL finished))completion {
  if (_mode == mode) {
    if (animateAlongside) {
      animateAlongside();
    }
    if (completion) {
      completion(YES);
    }
    return;
  }
  _mode = mode;

  [self updateShapeAndAllowResize:YES];

  [[self modeAnimator] modeDidChange:mode
                            animated:animated
                    animateAlongside:animateAlongside
                          completion:completion];
}

- (void)setMinimumSize:(CGSize)size
              forShape:(MDCFloatingButtonShape)shape
                inMode:(MDCFloatingButtonMode)mode {
  NSMutableDictionary *modeToMinimumSize = self.shapeToModeToMinimumSize[@(shape)];
  if (!modeToMinimumSize) {
    modeToMinimumSize = [@{} mutableCopy];
    self.shapeToModeToMinimumSize[@(shape)] = modeToMinimumSize;
  }
  modeToMinimumSize[@(mode)] = [NSValue valueWithCGSize:size];
  if (shape == _shape && mode == self.mode) {
    [self updateShapeAndAllowResize:YES];
  }
}

- (CGSize)minimumSizeForMode:(MDCFloatingButtonMode)mode {
  NSMutableDictionary *modeToMinimumSize = self.shapeToModeToMinimumSize[@(_shape)];
  if (!modeToMinimumSize) {
    return CGSizeZero;
  }

  NSValue *sizeValue = modeToMinimumSize[@(mode)];
  if (sizeValue) {
    return [sizeValue CGSizeValue];
  } else {
    return CGSizeZero;
  }
}

- (BOOL)updateMinimumSize {
  CGSize newSize = [self minimumSizeForMode:self.mode];
  if (CGSizeEqualToSize(newSize, self.minimumSize)) {
    return NO;
  }
  super.minimumSize = newSize;
  return YES;
}

- (void)setMaximumSize:(CGSize)size
              forShape:(MDCFloatingButtonShape)shape
                inMode:(MDCFloatingButtonMode)mode {
  NSMutableDictionary *modeToMaximumSize = self.shapeToModeToMaximumSize[@(shape)];
  if (!modeToMaximumSize) {
    modeToMaximumSize = [@{} mutableCopy];
    self.shapeToModeToMaximumSize[@(shape)] = modeToMaximumSize;
  }
  modeToMaximumSize[@(mode)] = [NSValue valueWithCGSize:size];
  if (shape == _shape && mode == self.mode) {
    [self updateShapeAndAllowResize:YES];
  }
}

- (CGSize)maximumSizeForMode:(MDCFloatingButtonMode)mode {
  NSMutableDictionary *modeToMaximumSize = self.shapeToModeToMaximumSize[@(_shape)];
  if (!modeToMaximumSize) {
    return CGSizeZero;
  }

  NSValue *sizeValue = modeToMaximumSize[@(mode)];
  if (sizeValue) {
    return [sizeValue CGSizeValue];
  } else {
    return CGSizeZero;
  }
}

- (BOOL)updateMaximumSize {
  CGSize newSize = [self maximumSizeForMode:self.mode];
  if (CGSizeEqualToSize(newSize, self.maximumSize)) {
    return NO;
  }
  super.maximumSize = newSize;
  return YES;
}

- (void)setContentEdgeInsets:(UIEdgeInsets)contentEdgeInsets
                    forShape:(MDCFloatingButtonShape)shape
                      inMode:(MDCFloatingButtonMode)mode {
  NSMutableDictionary *modeToContentEdgeInsets = self.shapeToModeToContentEdgeInsets[@(shape)];
  if (!modeToContentEdgeInsets) {
    modeToContentEdgeInsets = [@{} mutableCopy];
    self.shapeToModeToContentEdgeInsets[@(shape)] = modeToContentEdgeInsets;
  }
  modeToContentEdgeInsets[@(mode)] = [NSValue valueWithUIEdgeInsets:contentEdgeInsets];
  if (shape == _shape && mode == self.mode) {
    [self updateShapeAndAllowResize:YES];
  }
}

- (UIEdgeInsets)contentEdgeInsetsForMode:(MDCFloatingButtonMode)mode {
  NSMutableDictionary *modeToContentEdgeInsets = self.shapeToModeToContentEdgeInsets[@(_shape)];
  if (!modeToContentEdgeInsets) {
    return UIEdgeInsetsZero;
  }

  NSValue *insetsValue = modeToContentEdgeInsets[@(mode)];
  if (insetsValue) {
    return [insetsValue UIEdgeInsetsValue];
  } else {
    return UIEdgeInsetsZero;
  }
}

- (void)updateContentEdgeInsets {
  super.contentEdgeInsets = [self contentEdgeInsetsForMode:self.mode];
}

- (void)setHitAreaInsets:(UIEdgeInsets)insets
                forShape:(MDCFloatingButtonShape)shape
                  inMode:(MDCFloatingButtonMode)mode {
  NSMutableDictionary *modeToHitAreaInsets = self.shapeToModeToHitAreaInsets[@(shape)];
  if (!modeToHitAreaInsets) {
    modeToHitAreaInsets = [@{} mutableCopy];
    self.shapeToModeToHitAreaInsets[@(shape)] = modeToHitAreaInsets;
  }
  modeToHitAreaInsets[@(mode)] = [NSValue valueWithUIEdgeInsets:insets];
  if (shape == _shape && mode == self.mode) {
    [self updateShapeAndAllowResize:NO];
  }
}

- (UIEdgeInsets)hitAreaInsetsForMode:(MDCFloatingButtonMode)mode {
  NSMutableDictionary *modeToHitAreaInsets = self.shapeToModeToHitAreaInsets[@(_shape)];
  if (!modeToHitAreaInsets) {
    return UIEdgeInsetsZero;
  }

  NSValue *insetsValue = modeToHitAreaInsets[@(mode)];
  if (insetsValue) {
    return [insetsValue UIEdgeInsetsValue];
  } else {
    return UIEdgeInsetsZero;
  }
}

- (void)updateHitAreaInsets {
  super.hitAreaInsets = [self hitAreaInsetsForMode:self.mode];
}

- (void)setCenterVisibleArea:(BOOL)centerVisibleArea
                    forShape:(MDCFloatingButtonShape)shape
                      inMode:(MDCFloatingButtonMode)mode {
  NSMutableDictionary *modeToCenterVisibleArea = self.shapeToModeToCenterVisibleArea[@(shape)];
  if (!modeToCenterVisibleArea) {
    modeToCenterVisibleArea = [@{} mutableCopy];
    self.shapeToModeToCenterVisibleArea[@(shape)] = modeToCenterVisibleArea;
  }
  modeToCenterVisibleArea[@(mode)] = @(centerVisibleArea);
  if (shape == _shape && mode == self.mode) {
    [self updateShapeAndAllowResize:NO];
  }
}

- (BOOL)centerVisibleAreaForMode:(MDCFloatingButtonMode)mode {
  NSMutableDictionary *modeToCenterVisibleArea = self.shapeToModeToCenterVisibleArea[@(_shape)];
  if (!modeToCenterVisibleArea) {
    return NO;
  }

  return [modeToCenterVisibleArea[@(mode)] boolValue];
}

- (void)updateCenterVisibleArea {
  super.centerVisibleArea = [self centerVisibleAreaForMode:self.mode];
}

- (void)updateShapeAndAllowResize:(BOOL)allowsResize {
  BOOL minimumSizeChanged = [self updateMinimumSize];
  BOOL maximumSizeChanged = [self updateMaximumSize];
  [self updateContentEdgeInsets];
  [self updateHitAreaInsets];
  [self updateCenterVisibleArea];

  if (allowsResize && (minimumSizeChanged || maximumSizeChanged)) {
    [self invalidateIntrinsicContentSize];
    [self setNeedsLayout];
  }
}

@end
